package net.m2technologies.open_arm.transport.transaction;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import net.m2technologies.open_arm.transaction.ArmCorrelatorImpl;
import net.m2technologies.open_arm.transaction.ArmInterfaceImpl;
import net.m2technologies.open_arm.transaction.TransactionState;
import net.m2technologies.open_arm.transport.formatters.BlockedMessageFormatter;
import net.m2technologies.open_arm.transport.formatters.DefaultBlockedMessageFormatterImpl;
import net.m2technologies.open_arm.transport.formatters.DefaultStopMessageFormatterImpl;
import net.m2technologies.open_arm.transport.formatters.DefaultUnblockedMessageFormatterImpl;
import net.m2technologies.open_arm.transport.formatters.DefaultUpdateMessageFormatterImpl;
import net.m2technologies.open_arm.transport.formatters.MessageFormatterFactory;
import net.m2technologies.open_arm.transport.formatters.StopMessageFormatter;
import net.m2technologies.open_arm.transport.formatters.UnblockedMessageFormatter;
import net.m2technologies.open_arm.transport.formatters.UpdateMessageFormatter;

import org.opengroup.arm40.metric.ArmMetricGroup;
import org.opengroup.arm40.metric.ArmTransactionWithMetrics;
import org.opengroup.arm40.metric.ArmTransactionWithMetricsDefinition;
import org.opengroup.arm40.transaction.ArmApplication;
import org.opengroup.arm40.transaction.ArmCorrelator;
import org.opengroup.arm40.transaction.ArmTransaction;
import org.opengroup.arm40.transaction.ArmTransactionDefinition;
import org.opengroup.arm40.transaction.ArmTransactionFactory;
import org.opengroup.arm40.transaction.ArmUser;

/**
 * Copyright 2005 Mark Masterson<br> <br> Licensed under the Apache License, Version 2.0 (the "License");<br> you may
 * not use this file except in compliance with the License.<br> You may obtain a copy of the License at<br> <br>
 * http://www.apache.org/licenses/LICENSE-2.0<br> <br> Unless required by applicable law or agreed to in writing,
 * software<br> distributed under the License is distributed on an "AS IS" BASIS,<br> WITHOUT WARRANTIES OR CONDITIONS
 * OF ANY KIND, either express or implied.<br> See the License for the specific language governing permissions and<br>
 * limitations under the License.<br>
 * <p/>
 * <p>Description: This class implements the core functionality for all TransactionTransportMediators..</p>
 *
 * @author Mark Masterson
 * @version 0.010
 */
public abstract class AbstractTransactionMediatorImpl implements TransactionMediator {

//    private static final Logger logger = Logger.getLogger(AbstractTransactionMediatorImpl.class);

    private ArmTransaction transactionDelegate;
    private ExecutorService executor = Executors.newFixedThreadPool(7);

    /**
     * Creates an instance of this class.  This class has an association with an instance of ArmTransaction -- the
     * caller of this constructor must either provide that instance here, or provide it later with a call to
     * #setTransactionalDelegate.
     *
     * @param transactionDelegate
     */
    protected AbstractTransactionMediatorImpl(final Object transactionDelegate) {
        this.transactionDelegate = (ArmTransaction) transactionDelegate;
    }

    /**
     * This class has an association with an instance of ArmTransaction -- the caller must either provide that instance
     * here, or pass it to the constructor.
     *
     * @param transactionDelegate
     */
    public final void setTransactionalDelegate(final Object transactionDelegate) {
        this.transactionDelegate = (ArmTransaction) transactionDelegate;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @return an instance of the creating ArmTransactionFactory
     */
    protected ArmTransactionFactory getCreatingFactory() {
        final ArmTransactionFactory result;
        try {
            result = ((ArmInterfaceImpl) getDelegate()).getCreatingFactory();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * This method is not currently supported.
     *
     * @return UnsupportedOperationException
     *
     * @deprecated We may not ever implement this method!
     */
    public final int bindThread() {
        throw new UnsupportedOperationException(
                "This signature is just too stupid to contemplate. Damn C programmers.");
    }

    /**
     * Obtains the next locking handle from the transaction delegate, calls the hook method #doBlocked, and returns the
     * handle.
     *
     * @return the next locking handle -- locking handles are just an incremented counter, and they are not guaranteed
     *         to be uniquie across VM's or invocations of the same VM.
     */
    public final long blocked() {
        final long tmpHandle = ((TransactionState) getDelegate()).getBlockingHandle();
        this.executor.execute(new Runnable() {
            public void run() {
                doBlocked(tmpHandle);
            }
        });
//        doBlocked(tmpHandle);
        return tmpHandle;
    }

    /**
     * Concrete implementations implement this method to persist/record/transmit the event.
     *
     * @param tmpHandle
     */
    protected abstract void doBlocked(final long tmpHandle);

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @return the delegates' instance of ArmApplication
     */
    public final ArmApplication getApplication() {
        final ArmApplication result;
        try {
            result = getDelegate().getApplication();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @return the context URI, if set
     */
    public final String getContextURIValue() {
        final String result;
        try {
            result = getDelegate().getContextURIValue();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @param index of the desired context value
     *
     * @return the context value at the given index
     */
    public final String getContextValue(final int index) {
        final String result;
        try {
            result = getDelegate().getContextValue(index);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @return the delegates' instance of ArmCorrelator, if set
     */
    public final ArmCorrelator getCorrelator() {
        final ArmCorrelator result;
        try {
            result = getDelegate().getCorrelator();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @return the delegates' instance of the parent ArmCorrelator, if set
     */
    public final ArmCorrelator getParentCorrelator() {
        final ArmCorrelator result;
        try {
            result = getDelegate().getParentCorrelator();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @return the completion status of the last iteration of this transaction.
     */
    public final int getStatus() {
        final int result;
        try {
            result = getDelegate().getStatus();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @return the delegates' instance of ArmTransactionDefinition
     */
    public final ArmTransactionDefinition getDefinition() {
        final ArmTransactionDefinition result;
        try {
            result = getDelegate().getDefinition();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @return the delegates' instance of ArmUser
     */
    public final ArmUser getUser() {
        final ArmUser result;
        try {
            result = getDelegate().getUser();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @return true if the trace flag is set on the delegate, otherwise false.
     */
    public final boolean isTraceRequested() {
        return ((TransactionState) getDelegate()).getTraceState();
    }

    /**
     * Resets all of the transaction delimited attributes of the delegate object.
     *
     * @return Zero.  Always.  No kidding!
     */
    public final int reset() {
        ((TransactionState) getDelegate()).setStartTime(0);
        ((TransactionState) getDelegate()).setArrivalTime(0);
        ((TransactionState) getDelegate()).setTraceState(false);
        ((TransactionState) getDelegate()).setParentCorrelator(null);
        return 0;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @return the delegates' instance of arrival time
     */
    public final int setArrivalTime() {
        ((TransactionState) getDelegate()).setArrivalTime(System.currentTimeMillis());
        return 0;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @param value
     *
     * @return The ARM error code.  Zero means 'No problem!'
     */
    public final int setContextURIValue(final String value) {
        final int result;
        try {
            result = getDelegate().setContextURIValue(value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @param index
     * @param value
     *
     * @return The ARM error code.  Zero means 'No problem!'
     */
    public final int setContextValue(final int index, final String value) {
        final int result;
        try {
            result = getDelegate().setContextValue(index, value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @param traceState
     *
     * @return The ARM error code.  Zero means 'No problem!'
     */
    public final int setTraceRequested(final boolean traceState) {
        ((TransactionState) getDelegate()).setTraceState(traceState);
        return 0;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @param user
     *
     * @return The ARM error code.  Zero means 'No problem!'
     */
    public final int setUser(final ArmUser user) {
        final int result;
        try {
            result = getDelegate().setUser(user);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given, and
     * sets the start time to System.currentTimeMillis().
     *
     * @return Zero.  Always.  No kidding.
     */
    public final int start() {
        ((TransactionState) getDelegate()).setStartTime(System.currentTimeMillis());
        return 0;
    }

    /**
     * See {@link #start(ArmCorrelator parentCorr)}
     *
     * @param parentCorr
     *
     * @return the parent correlator
     */
    public final int start(final byte[] parentCorr) {
        return start(new ArmCorrelatorImpl(parentCorr, getCreatingFactory()));
    }

    /**
     * This method signature is not supported.
     *
     * @param parentCorr
     * @param offset
     *
     * @return UnsupportedOperationException
     *
     * @deprecated We may never implement this method!
     */
    public final int start(final byte[] parentCorr, final int offset) {
        throw new UnsupportedOperationException(
                "This signature is just too stupid to contemplate. Damn C programmers.");
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given,
     * sets the start time to System.currentTimeMillis(), and the parent correlator to the value of the parameter passed
     * to this method.
     *
     * @param parentCorr
     *
     * @return #start's error code
     */
    public final int start(final ArmCorrelator parentCorr) {
        ((TransactionState) getDelegate()).setParentCorrelator(parentCorr);
        return start();
    }

    /**
     * See {@link #stop(int status, String diagnosticDetail)}
     *
     * @param status
     *
     * @return error code
     */
    public final int stop(final int status) {
        return stop(status, null);
    }

    /**
     * This method obtains the stop time via System.currentTimeMillis(), calculates the elapsed time by subtracting the
     * start time recorded in the transactional delegates' instance from this value, calculates the total elapsed time
     * (if needed) by subtracting the transactional delegates' instance of the arrival time from the stop time, and
     * calls the protected hook method #doStop to allow concrete implementations to record/persist the event.
     *
     * @param status
     * @param diagnosticDetail
     *
     * @return Zero as placeholder error code
     */
    public final int stop(final int status, final String diagnosticDetail) {
        final long stopTime = System.currentTimeMillis();
        final long elapsedTime = stopTime - ((TransactionState) getDelegate()).getStartTime();
        final long totalElapsedTime;
        if (0 < ((TransactionState) getDelegate()).getArrivalTime()) {
            totalElapsedTime = stopTime - ((TransactionState) getDelegate()).getArrivalTime();
        } else
            totalElapsedTime = 0;
        this.executor.execute(new Runnable() {
            public void run() {
                doStop(elapsedTime, totalElapsedTime, status, diagnosticDetail);
            }
        });
//        doStop(elapsedTime, totalElapsedTime, status, diagnosticDetail);
        return 0;
    }

    /**
     * Concrete implementations implement this method to persist/record/transmit the event.
     *
     * @param elapsedTime
     * @param totalElapsedTime
     * @param status
     * @param diagnosticDetail
     */
    protected abstract void doStop(final long elapsedTime,
                                   long totalElapsedTime,
                                   final int status,
                                   final String diagnosticDetail);

    /**
     * This method is not supported.
     *
     * @return UnsupportedOperationException
     *
     * @deprecated We may never implement this method!
     */
    public final int unbindThread() {
        throw new UnsupportedOperationException(
                "This signature is just too stupid to contemplate. Damn C programmers.");
    }

    /**
     * Calls the #doUnblocked hook method.
     *
     * @param blockHandle
     *
     * @return Zero as placeholder error code
     */
    public final int unblocked(final long blockHandle) {
        this.executor.execute(new Runnable() {
            public void run() {
                doUnblocked(blockHandle);
            }
        });
//        doUnblocked(blockHandle);
        return 0;
    }

    /**
     * Concrete implementations implement this method to persist/record/transmit the event.
     *
     * @param blockHandle
     */
    protected abstract void doUnblocked(final long blockHandle);

    /**
     * Obtains the update timestamp via System.currentTimeMillis().  Calculates running time and (if needed) total
     * running time, by subtracting start and arrival time, respectively, from this value.  Then calls the #doUpdate
     * hook method to persist/record the event.
     *
     * @return Zero as placeholder error code
     */
    public final int update() {
        final long updateTime = System.currentTimeMillis();
        final long runningTime = updateTime - ((TransactionState) getDelegate()).getStartTime();
        final long totalRunningTime;
        if (0 < ((TransactionState) getDelegate()).getArrivalTime()) {
            totalRunningTime = updateTime - ((TransactionState) getDelegate()).getArrivalTime();
        } else
            totalRunningTime = 0;
        this.executor.execute(new Runnable() {
            public void run() {
                doUpdate(runningTime, totalRunningTime);
            }
        });
//        doUpdate(runningTime, totalRunningTime);
        return 0;
    }

    /**
     * Concrete implementations implement this method to persist/record/transmit the event.
     *
     * @param runningTime
     * @param totalRunningTime
     */
    protected abstract void doUpdate(final long runningTime, long totalRunningTime);

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @return error code
     */
    public int getErrorCode() {
        final int result;
        try {
            result = getDelegate().getErrorCode();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @param errorCode
     *
     * @return error code
     */
    public int setErrorCode(final int errorCode) {
        final int result;
        try {
            result = getDelegate().setErrorCode(errorCode);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     *
     * @param errorCode
     *
     * @return the text message that matches the error code param
     */
    public String getErrorMessage(final int errorCode) {
        final String result;
        try {
            result = getDelegate().getErrorMessage(errorCode);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     * This method can throw a ClassCast Exception, if the delegate being used is not an instance of
     * ArmTransactionWithMetrics.
     *
     * @return ArmTransactionWithMetricsDefinition
     */
    public ArmTransactionWithMetricsDefinition getTransactionWithMetricsDefinition() {
        return ((ArmTransactionWithMetrics) getDelegate()).getTransactionWithMetricsDefinition();
    }

    /**
     * Invokes the method of the same name on the delegate instance of ArmTransaction that the constructor is given.
     * This method can throw a ClassCast Exception, if the delegate being used is not an instance of
     * ArmTransactionWithMetrics.
     *
     * @return ArmMetricGroup
     */
    public ArmMetricGroup getMetricGroup() {
        return ((ArmTransactionWithMetrics) getDelegate()).getMetricGroup();
    }

    protected final ArmTransaction getDelegate() {
        return transactionDelegate;
    }

    protected String getBlockedMessage(final long tmpHandle) {
        return getBlockedMessageFormatter().getBlockedMessage(tmpHandle,
                                                              ((TransactionState) getDelegate()).getName(),
                                                              getDefinition().getName(),
                                                              getDefinition().getApplicationDefinition().getName(),
                                                              handleCorrelationId());
    }

    private BlockedMessageFormatter getBlockedMessageFormatter() {
        BlockedMessageFormatter result;
        try {
            result
                    = (BlockedMessageFormatter) MessageFormatterFactory.getMessageFormatterInstance(
                    BlockedMessageFormatter.class);
        } catch (ClassNotFoundException e) {
            result = new DefaultBlockedMessageFormatterImpl();
        }
        return result;
    }

    protected String getStopMessage(final long elapsedTime,
                                    final long totalElapsedTime,
                                    final int status,
                                    final String diagnosticDetail) {
        return getStopMessageFormatter().getStopMessage(elapsedTime,
                                                        totalElapsedTime,
                                                        status,
                                                        diagnosticDetail,
                                                        ((TransactionState) getDelegate()).getName(),
                                                        getDefinition().getName(),
                                                        getDefinition().getApplicationDefinition().getName(),
                                                        handleCorrelationId(),
                                                        getDelegate());
    }

    private StopMessageFormatter getStopMessageFormatter() {
        StopMessageFormatter result;
        try {
            result
                    = (StopMessageFormatter) MessageFormatterFactory.getMessageFormatterInstance(StopMessageFormatter.class);
        } catch (ClassNotFoundException e) {
            result = new DefaultStopMessageFormatterImpl();
        }
        return result;
    }

    protected String getUnblockedMessage(final long blockHandle) {
        return getUnblockedMessageFormatter().getUnblockedMessage(blockHandle,
                                                                  ((TransactionState) getDelegate()).getName(),
                                                                  getDefinition().getName(),
                                                                  getDefinition().getApplicationDefinition().getName(),
                                                                  handleCorrelationId());
    }

    private UnblockedMessageFormatter getUnblockedMessageFormatter() {
        UnblockedMessageFormatter result;
        try {
            result
                    = (UnblockedMessageFormatter) MessageFormatterFactory.getMessageFormatterInstance(
                    UnblockedMessageFormatter.class);
        } catch (ClassNotFoundException e) {
            result = new DefaultUnblockedMessageFormatterImpl();
        }
        return result;
    }

    protected String getUpdateMessage(final long runningTime, final long totalRunningTime) {
        return getUpdateMessageFormatter().getStopMessage(runningTime,
                                                          totalRunningTime,
                                                          ((TransactionState) getDelegate()).getName(),
                                                          getDefinition().getName(),
                                                          getDefinition().getApplicationDefinition().getName(),
                                                          handleCorrelationId());

    }

    private UpdateMessageFormatter getUpdateMessageFormatter() {
        UpdateMessageFormatter result;
        try {
            result
                    = (UpdateMessageFormatter) MessageFormatterFactory.getMessageFormatterInstance(
                    UpdateMessageFormatter.class);
        } catch (ClassNotFoundException e) {
            result = new DefaultUpdateMessageFormatterImpl();
        }
        return result;
    }

    private String handleCorrelationId() {
        return null != ((TransactionState) getDelegate()).getParentCorrelator()
               ? new String(((TransactionState) getDelegate()).getParentCorrelator()
                .getBytes())
               : "";
    }
}